package com.mocoder.chatdemo.protocol;

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

import com.mocoder.chatdemo.model.CallbackFunc;
import com.mocoder.chatdemo.model.ChatMsg;
import com.mocoder.chatdemo.model.MsgModel;
import com.mocoder.chatdemo.model.UserInfo;

/**
 * Created by Administrator on 2018/3/9.
 */
public class ProtocolHelper {

    public static void sentUserInfoUpdateResp(MsgModel msgObj) {
        byte[] respBytes = UserInfo.encode(getUserInfo(null), false);
        NetHelper.sentUDPMsg(respBytes, msgObj.getHost(), null, false);
    }

    public static void sentEnsureResponse(MsgModel msgObj) {
        NetHelper.sentUDPMsg(new byte[] {1}, msgObj.getHost(), null, false, msgObj.getPort());
    }

    public static void sendChatMsgData(boolean isChatRoom, String msg, InetAddress msgFrom, CallbackFunc callback) {
        byte[] bytes = ChatMsg.encode(isChatRoom, msg);
        if (isChatRoom) {
            NetHelper.sentBroadcastMsg(bytes, callback, true);
        } else {
            NetHelper.sentUDPMsg(bytes, msgFrom, callback, true);
        }
    }

    public static void sendUserScanBroadcase(UserInfo userInfo) {
        byte[] bytes = UserInfo.encode(userInfo, true);
        NetHelper.sentBroadcastMsg(bytes, null, false);
    }

    public static boolean isLocalAddr(InetAddress address) {
        return address.equals(NetHelper.LOCAL_ADDR);
    }

    public static String getUserName(InetAddress address) {
        return userNameMap.get(address.getHostAddress()).getName();
    }

    public static UserInfo getUserInfo(InetAddress address) {
        if (address == null) {
            address = NetHelper.LOCAL_ADDR;
        }
        return userNameMap.get(address.getHostAddress());
    }

    private static Map<String, UserInfo> userNameMap = new HashMap<>();

    public static void setUserInfo(InetAddress address, UserInfo userInfo) {
        if (address == null) {
            address = NetHelper.LOCAL_ADDR;
        }
        if (userInfo.getName() == null || userInfo.getName().trim().equals("")) {
            userInfo.setName(NetHelper.LOCAL_ADDR.getHostAddress());
        }
        userNameMap.put(address.getHostAddress(), userInfo);
    }

    public static InetAddress getLocalAdress() {
        return NetHelper.LOCAL_ADDR;
    }

    private static class NetHelper {

        public static final InetAddress LOCAL_ADDR;

        private static final InetAddress BROADCAST_ADDR;

        private static final ExecutorService EXECUTOR;

        static {
            LOCAL_ADDR = getLocalAddress();
            BROADCAST_ADDR = getBroadcastAddress();
            EXECUTOR = new ThreadPoolExecutor(10, 10, 10L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(),
                new ThreadFactory() {
                    private AtomicInteger counter = new AtomicInteger(0);

                    @Override
                    public Thread newThread(Runnable r) {
                        Thread t = new Thread(r);
                        t.setName("chatbox-net-pool1-" + counter.getAndIncrement());
                        t.setDaemon(true);
                        return t;
                    }
                }, new ThreadPoolExecutor.AbortPolicy());
            Runtime.getRuntime().addShutdownHook(new Thread() {
                @Override
                public void run() {
                    EXECUTOR.shutdown();
                }
            });
        }

        public static void sentUDPMsg(final byte[] msg, final InetAddress address, CallbackFunc callback,
                                      boolean waitResp,
                                      final int... port) {
            Runnable runnable = new Runnable() {
                public void run() {
                    DatagramSocket socket = null;
                    try {
                        socket = new DatagramSocket(0, LOCAL_ADDR);
                        socket.setSoTimeout(500);
                        DatagramPacket packet = new DatagramPacket(msg, 0, msg.length, address,
                            port.length < 1 ? 3333 : port[0]);
                        socket.send(packet);
                        if (waitResp) {
                            socket.receive(packet);
                        }
                        if (callback != null) {
                            try {
                                callback.success(null);
                            } catch (Exception e) {
                            }
                        }
                    } catch (Exception e) {
                        if (callback != null) {
                            try {
                                callback.fail(e, null);
                            } catch (Exception e1) {
                            }
                        }
                        e.printStackTrace();
                    } finally {
                        if (socket != null) {
                            socket.close();
                        }
                    }
                }
            };
            EXECUTOR.submit(runnable);
        }

        public static void sentBroadcastMsg(final byte[] msg, final CallbackFunc callback, boolean waitResp,
                                            int... port) {
            Runnable runnable = new Runnable() {
                @Override
                public void run() {
                    DatagramSocket socket = null;
                    try {
                        socket = new DatagramSocket(0, LOCAL_ADDR);
                        socket.setSoTimeout(500);
                        socket.setBroadcast(true);
                        DatagramPacket packet = new DatagramPacket(msg, 0, msg.length,
                            NetHelper.BROADCAST_ADDR,
                            port.length < 1 ? 3333 : port[0]);
                        socket.send(packet);
                        if (waitResp) {
                            socket.receive(packet);
                        }
                        if (callback != null) {
                            try {
                                callback.success(null);
                            } catch (Exception e) {
                            }
                        }
                    } catch (Exception e) {
                        if (callback != null) {
                            try {
                                callback.fail(e, null);
                            } catch (Exception e1) {
                            }
                        }
                        e.printStackTrace();
                    } finally {
                        if (socket != null) {
                            socket.close();
                        }
                    }
                }
            };
            EXECUTOR.submit(runnable);
        }

        public static InetAddress getLocalAddress() {
            try {
                Enumeration<NetworkInterface> niEnum = NetworkInterface.getNetworkInterfaces();
                InetAddress theLastRealAddr = null;
                while (niEnum.hasMoreElements()) {
                    NetworkInterface e = niEnum.nextElement();
                    Enumeration<InetAddress> addList = e.getInetAddresses();
                    if (e.isUp() && !e.isLoopback() && !e.isVirtual() && e.getInetAddresses().hasMoreElements()) {
                        while (addList.hasMoreElements()) {
                            InetAddress address = addList.nextElement();
                            if (address.getHostAddress().length() <= 16) {
                                theLastRealAddr = address;
                            }
                        }
                    }
                }
                return theLastRealAddr;
            } catch (Exception e1) {
                e1.printStackTrace();
            }
            return null;
        }

        public static InetAddress getBroadcastAddress() {
            try {
                return InetAddress.getByName("255.255.255.255");
            } catch (Exception e) {
                return null;
            }
        }
    }

}
